package service

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"

	"wxpay/internal/conf"
	"wxpay/internal/dao"
	"wxpay/internal/dto"
	"wxpay/internal/model"
	"wxpay/pkg/merrors"
	"wxpay/pkg/util"

	"gorm.io/gorm"

	mchApi "wxpay/pkg/wechat_api"
)

type transService struct {
	d dao.DataStore
}

func NewTransService(d dao.DataStore) *transService {
	return &transService{d: d}
}

func tradeDTOToJSAPIPrepayRequest(t dto.TradeDTO) (r mchApi.JsApiPrepayRequest) {
	r = mchApi.JsApiPrepayRequest{
		SpAppID:     t.AppID,
		SpMchID:     t.MchID,
		SubAppID:    t.SubAppID,
		SubMchID:    t.SubMchID,
		Description: t.Description,
		OutTradeNo:  t.OutTradeNo,
		Attach:      t.Description,
		NotifyUrl:   conf.Conf.Callback.Endpoint + "/api/v1/wxpay/notify",
		Amount: struct {
			Total    uint   `json:"total"`
			Currency string `json:"currency"`
		}{Total: t.AmountInfo.Total, Currency: t.AmountInfo.Currency},
		Payer: struct {
			SpOpenID  string `json:"sp_openid"`
			SubOpenID string `json:"sub_openid"`
		}{SpOpenID: t.PayerInfo.SpOpenID, SubOpenID: t.PayerInfo.SubOpenID},
	}
	if t.SettleInfo != nil {
		r.SettleInfo = struct {
			ProfitSharing bool `json:"profit_sharing"`
			SubsidyAmount int  `json:"subsidy_amount"`
		}{t.SettleInfo.ProfitSharing, t.SettleInfo.SubsidyAmount}
	}
	r.TimeExpire = util.TimePtrToString(&t.ExpireTime, util.LayoutSecondWithTimeZoneAndT)
	return
}

// 创建JSAPI交易 幂等可重入
func (s *transService) JSAPIPrepay(ctx context.Context, tradeDto dto.TradeDTO) (r *dto.PrepayDTO, err error) {
	const op merrors.Op = "transService.JSAPIPrepay"
	client, err := NewMchService(s.d).BuildMchApiClient(ctx, tradeDto.MchID)
	if err != nil {
		return nil, merrors.E(op, merrors.Invalid, err)
	}
	var prepayID string
	// 查询交易是否已创建
	trade, err := s.d.Trade().QueryByOutTradeNo(ctx, s.d.GetTx(ctx), tradeDto.OutTradeNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 交易不存在, 先创建交易
		trade = dto.AdaptTrade(tradeDto)
		if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
			if e := s.d.Trade().Create(ctx, tx, trade); e != nil {
				return e
			}
			return nil
		}); err != nil {
			return nil, merrors.E(op, merrors.Invalid, err)
		}
		//	调微信支付生成prePayID
		req := tradeDTOToJSAPIPrepayRequest(tradeDto)
		fmt.Printf("JSAPIPrepay=>%+v", req)
		apiResp, err := client.JsApiPrepay(ctx, req)
		if err != nil {
			return nil, merrors.E(op, merrors.Invalid, merrors.ErrWxApi, err)
		}
		prepayID = apiResp.PrepayID
		// 更新prepay_id
		trade.SetPrepayID(prepayID)
		trade.UpdateTime = util.GetNow()
		tradeDto.PrepayID = prepayID
		if err := s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
			if e := s.d.Trade().UpdatePrepayIDInTx(ctx, tx, trade); err != nil {
				return e
			}
			// 创建查询任务用来兜底，防止回调接口异常导致交易状态和微信侧不一致
			if e := s.createTradeQueryTask(ctx, tx, trade); e != nil {
				return e
			}
			return nil
		}); err != nil {
			return nil, err
		}
	}
	if err != nil {
		return nil, merrors.E(op, merrors.Invalid, err)
	}
	appId := trade.AppID
	if trade.SubAppID.Valid {
		appId = trade.SubAppID.String
	}
	// 生成JSAPI调起支付需要的结构体
	payReq, err := client.GenJsApiPayRequest(mchApi.JsApiPayRequest{
		AppID:     appId,
		TimeStamp: strconv.Itoa(int(util.GetNow().Unix())),
		Package:   fmt.Sprintf("prepay_id=%s", trade.PrepayID.String),
	})
	if err != nil {
		return nil, merrors.E(op, merrors.Invalid, merrors.ErrWxApi, err)
	}

	r = &dto.PrepayDTO{
		AppID:      payReq.AppID,
		TimeStamp:  payReq.TimeStamp,
		Nonce:      payReq.Nonce,
		Package:    payReq.Package,
		SignType:   payReq.SignType,
		PaySign:    payReq.PaySign,
		ExpireTime: trade.ExpireTime,
	}
	return
}

// 创建交易查询任务
func (s *transService) createTradeQueryTask(ctx context.Context, tx *gorm.DB, trade *model.Trade) error {
	payload := model.NewTradeQueryTask(trade)
	ps, _ := json.Marshal(&payload)
	// 延后到expire_time后执行
	msg := model.NewMessage(model.TopicTradeQuery, ps, trade.ExpireTime.Sub(util.GetNow()))
	if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
		return e
	}
	return nil
}

// 查询交易
func (s *transService) TradeQueryByOutTradeNo(ctx context.Context, brandID string, outTradeNo string) (r *dto.TradeDTO, err error) {
	const op merrors.Op = "transService.TradeQueryByOutTradeNo"
	trade, err := s.d.Trade().QueryByOutTradeNo(ctx, s.d.GetTx(ctx), outTradeNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		return nil, merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "交易不存在")
	}
	if err != nil {
		return nil, err
	}
	if !trade.DoesBelongTo(brandID) {
		return nil, merrors.E(op, merrors.Permission, "无权查看")
	}
	r = dto.ConvertTrade(trade)
	return
}

// 通知交易成功
func (s *transService) NotifyPayment(ctx context.Context, notifyDTO dto.PayNotificationDTO) (err error) {
	trade, err := s.d.Trade().QueryByOutTradeNo(ctx, s.d.GetTx(ctx), notifyDTO.OutTradeNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 错误的通知，交易不存在直接返回
		return nil
	}
	if err != nil {
		return
	}
	return s.success(ctx, trade, notifyDTO)
}

// 交易成功
func (s *transService) success(ctx context.Context, trade *model.Trade, notifyDTO dto.PayNotificationDTO) (err error) {
	const op merrors.Op = "transService.success"
	msg := &model.Message{}
	if err := s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		// 加锁
		trade, e := s.d.Lock().LockTrade(ctx, tx, trade.ID)
		if e != nil {
			return e
		}
		if trade.SuccessTime != nil {
			// 幂等处理
			return nil
		}
		// 更新交易状态
		successT, e := util.ParseTimePtr(util.LayoutSecondWithTimeZoneAndT, notifyDTO.SuccessTime)
		if e != nil {
			return merrors.E(op, merrors.InvalidArgument, merrors.ErrInvalidArguments, e)
		}
		trade.UpdateVoucher(notifyDTO.TradeType, notifyDTO.TradeState, notifyDTO.TradeStateDesc, notifyDTO.TransactionID, successT)
		if e := s.d.Trade().UpdateVoucher(ctx, tx, trade); e != nil {
			return e
		}
		// 保存消息
		payload := model.NewPayNotification(trade)
		ps, _ := json.Marshal(&payload)
		msg = model.NewMessage(model.TopicPayedNotify, ps, 0)
		if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	// 发送消息
	if err = NewMessageService(s.d).Emit(ctx, msg.ID); err != nil {
		return
	}
	return
}

// 交易失败
func (s *transService) fail(ctx context.Context, trade *model.Trade, notifyDTO dto.PayNotificationDTO) (err error) {
	if err := s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		// 加锁
		trade, e := s.d.Lock().LockTrade(ctx, tx, trade.ID)
		if e != nil {
			return e
		}
		if trade.SuccessTime != nil {
			// 幂等处理
			return nil
		}
		// 更新交易状态
		trade.UpdateVoucher(notifyDTO.TradeType, notifyDTO.TradeState, notifyDTO.TradeStateDesc, notifyDTO.TransactionID, nil)
		if e := s.d.Trade().UpdateVoucher(ctx, tx, trade); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return
}

// 交易中间状态同步
func (s *transService) interState(ctx context.Context, trade *model.Trade, notifyDTO dto.PayNotificationDTO) (err error) {
	if err := s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		// 加锁
		trade, e := s.d.Lock().LockTrade(ctx, tx, trade.ID)
		if e != nil {
			return e
		}
		if trade.SuccessTime != nil {
			// 幂等处理
			return nil
		}
		// 更新交易状态
		trade.UpdateVoucher(notifyDTO.TradeType, notifyDTO.TradeState, notifyDTO.TradeStateDesc, notifyDTO.TransactionID, nil)
		if e := s.d.Trade().UpdateVoucher(ctx, tx, trade); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return
}

// 关闭交易
func (s *transService) TradeClose(ctx context.Context, brandID string, outTradeNo string) (err error) {
	const op merrors.Op = "transService.TradeClose"
	trade, err := s.d.Trade().QueryByOutTradeNo(ctx, s.d.GetTx(ctx), outTradeNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		return merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "交易不存在")
	}
	if err != nil {
		return err
	}
	if !trade.DoesBelongTo(brandID) {
		return merrors.E(op, merrors.Permission, "无权查看")
	}
	client, err := NewMchService(s.d).BuildMchApiClient(ctx, trade.MchID)
	if err != nil {
		return merrors.E(op, merrors.Invalid, err)
	}
	apiReq := mchApi.CloseOrderRequest{
		SpMchID:    trade.MchID,
		SubMchID:   trade.SubMchID,
		OutTradeNo: trade.OutTradeNo,
	}
	if err = client.Close(ctx, apiReq); err != nil {
		return merrors.E(op, merrors.Invalid, merrors.ErrWxApi, err)
	}
	if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		tr, e := s.d.Lock().LockTrade(ctx, tx, trade.ID)
		if e != nil {
			return e
		}
		if e = tr.Close(); e != nil {
			return e
		}
		if e := s.d.Trade().Close(ctx, tx, tr); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return
	}

	return nil
}

// 同步交易状态
func (s *transService) SyncTrade(ctx context.Context, outTradeNo string) (err error) {
	const op merrors.Op = "transService.SyncTrade"
	trade, err := s.d.Trade().QueryByOutTradeNo(ctx, s.d.GetTx(ctx), outTradeNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 错误的通知，交易不存在直接返回
		err = merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "交易不存在")
		return
	}
	if err != nil {
		return
	}
	if trade.IsFinalState() {
		// 已经是最终状态，直接返回
		return nil
	}
	query := mchApi.QueryPayResultByOutRequestNoRequest{
		SpMchID:    trade.MchID,
		SubMchID:   trade.SubMchID,
		OutTradeNo: trade.OutTradeNo,
	}
	client, err := NewMchService(s.d).BuildMchApiClient(ctx, trade.MchID)
	if err != nil {
		return
	}
	resp, err := client.PayResultQueryByOutRequestNo(ctx, query)
	if err != nil {
		fmt.Printf("PayResultQueryByOutRequestNo err=>:%+v", err)
		err = merrors.E(op, merrors.Invalid, merrors.ErrWxApi, "查询微信支付交易失败")
		return
	}
	notifyDTO := dto.PayNotificationDTO{
		SpAppID:        resp.SpAppID,
		SpMchID:        resp.SpMchID,
		SubAppID:       resp.SubAppID,
		SubMchID:       resp.SubMchID,
		OutTradeNo:     resp.OutTradeNo,
		TransactionID:  resp.TransactionID,
		TradeType:      resp.TradeType,
		TradeState:     resp.TradeState,
		TradeStateDesc: resp.TradeStateDesc,
		BankType:       resp.BankType,
		Attach:         resp.Attach,
		SuccessTime:    resp.SuccessTime,
	}
	switch resp.TradeState {
	case "SUCCESS":
		//	支付成功 - 终态
		err = s.success(ctx, trade, notifyDTO)
	case "CLOSED", "PAYERROR", "REVOKED":
		//	支付失败 - 终态
		err = s.fail(ctx, trade, notifyDTO)
	default:
		//	同步状态 - 中间态
		err = s.interState(ctx, trade, notifyDTO)
	}
	return
}
